package com.app.store;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.log4j.Logger;

import com.app.cache.CacheClient;
import com.app.cache.CacheFactory;
import com.app.config.DCacheConfig;
import com.app.exception.DCacheException;
import com.app.util.StringUtils;


public abstract class AbstractStore implements Store {

	protected Logger logger = Logger.getLogger(this.getClass());
	public static AtomicLong putTimes = new AtomicLong();
	public static AtomicLong putListTimes = new AtomicLong();
	public static AtomicLong getFromMemTimes = new AtomicLong();
	public static AtomicLong getFromDBTimes = new AtomicLong();
	public static AtomicLong delTimes = new AtomicLong();
	public static AtomicLong delListTimes = new AtomicLong();
	public static final String LOCK_KEY = "LOCK_RECORD";
	public static final String ONLY_CACHE_KEY = "ONLY_CACHE_KEY";//只存入缓存的key
	public static final int LOCK_TIMEOUT = 60;//秒
	protected final DCacheConfig cfg = DCacheConfig.getInstance();
	protected final CacheClient client = CacheFactory.getRedisClient();
	private int storeType;
	/**
	 * 以下日志采集  需log4j debug模式支持
	 * key class
	 */
	public static ConcurrentHashMap<String,AtomicLong> put = new ConcurrentHashMap<String, AtomicLong>();
	public static ConcurrentHashMap<String,AtomicLong> get = new ConcurrentHashMap<String, AtomicLong>();
	public static ConcurrentHashMap<String,AtomicLong> del = new ConcurrentHashMap<String, AtomicLong>();
	public static final int BATCH_EXECUTE_SIZE = 1000;
	
	public static final String UPDATE_QUEUE_KEY_LEVEL1 = "updateQueueTable|"+DCacheConfig.getInstance().getDatabaseName()+"|";
	
	public static final String UPDATE_QUEUE_KEY_LEVEL2 = "updateQueueRecord|"+DCacheConfig.getInstance().getDatabaseName()+"|";
	
	public static final String DEL_QUEUE_KEY = "delQueueTable|"+DCacheConfig.getInstance().getDatabaseName()+"|";
	
	AbstractStore(int type) {
		this.storeType = type;
	}
	
	/**
	 * 最终为set形式
	 * @param tableName
	 * @return
	 */
	public String genUpdateQueueKeyLevel1(String tableName) {
		if(StringUtils.isNullOrEmpty(tableName)) {
			throw new DCacheException("tableName is null");
		}
		StringBuilder sb = new StringBuilder();
		sb.append(UPDATE_QUEUE_KEY_LEVEL1);
		sb.append(tableName);
		return sb.toString();
	}
	
	/**
	 * 最终为map形式
	 * @param tableName
	 * @param id
	 * @return
	 */
	protected String genUpdateQueueKeyLevel2(String tableName,long id) {
		if(StringUtils.isNullOrEmpty(tableName)) {
			throw new DCacheException("tableName is null");
		}
		StringBuilder sb = new StringBuilder();
		sb.append(UPDATE_QUEUE_KEY_LEVEL2);
		sb.append(tableName);
		sb.append("|");
		sb.append(id);
		return sb.toString();
	}
	
	public String genDelQueueKey(String tableName) {
		if(StringUtils.isNullOrEmpty(tableName)) {
			throw new DCacheException("tableName is null");
		}
		StringBuilder sb = new StringBuilder();
		sb.append(DEL_QUEUE_KEY);
		sb.append(tableName);
		return sb.toString();
	}
	
	protected boolean delQueueContainsKey(String tableName,long id) {
		String delQueueKey = genDelQueueKey(tableName);
		return client.scontains(delQueueKey, String.valueOf(id));
	}
	
	protected String genMemcKey(String tableName,String key) {
		if(StringUtils.isNullOrEmpty(tableName,key)) {
			throw new DCacheException("tableName or key is null");
		}
		 String dbname=cfg.getDatabaseName();
		 StringBuilder name=new StringBuilder();
		 name.append(dbname);
		 name.append("|");
		 name.append(tableName);
		 name.append("|");
		 name.append(key);
		 return name.toString();
	 }
	 
	protected String genMemcKey(String tableName,long id) {
		 return genMemcKey(tableName, String.valueOf(id));
	 }
	
	/**
     * 放入更新队列
     * @param tableName
     * @param id
     * @param value
     * @param indexAttr
     * @param indexValue
     * @param changed
     * @throws DCacheException
     */
	protected void putUpdateQueue(String tableName, long id,Map<String, String> changed) throws DCacheException {
		//先移除删除队列数据
		removeDelQueue(tableName, id);
		//要先放memkey2
		String memKey2 = genUpdateQueueKeyLevel2(tableName, id);//map形式 
		client.hmset(memKey2, changed);
		
		String memKey1 = genUpdateQueueKeyLevel1(tableName);//set形式
		if(!client.scontains(memKey1, memKey2)) {
			client.sadd(memKey1, memKey2);
		}
	}
    
	/**
	 * 一:更新队列<p>
	 * 数据放入更新队列分两步<p>
	 * 1. 采用redis的map数据类型 key为updateQueueRecord|数据库名称|数据表名称|数据主键;<p>
	 * 	value为map形式,map中 放如要更新的数据其中key==>columnName value==>columnValue;若表结构为kv形式则map中key分别为{@link KeyValueStore#COLUMN_KEY},{@link KeyValueStore#COLUMN_VALUE},个索引表的名称<p>
	 * 2.采用redis的set数据类型 key为updateQueueTable|数据库名称|数据表名称<p>
	 * 	value为set形式,set的值为第一步中的key(即:updateQueueRecord|数据库名称|数据表名称|数据主键)<p>
	 * <p>
	 * <p>
	 * 二:删除队列<p>
	 * 	删除数据只有一步,采用redis的set数据类型,key为delQueueTable|数据库名称|数据表名称<p>
	 * 	value为set形式,set的值为待删除的数据主键<p>
	 * @param tableName
	 * @return
	 */
	public void putUpdateQueue(String tableName, List<Long> keyList,List<Map<String, String>> changedList) throws DCacheException {
		//先移除删除队列数据
		removeDelQueue(tableName, (Long[]) keyList.toArray());
		//要先放memkey2
		String[] memkeys = new String[keyList.size()];
		for(int i=0;i<keyList.size();i++) {
			memkeys[i] = genUpdateQueueKeyLevel2(tableName, keyList.get(i));
			client.hmset(memkeys[i], changedList.get(i));
		}
		
		String memKey1 = genUpdateQueueKeyLevel1(tableName);//set形式
		client.sadd(memKey1, memkeys);
	}
	
	protected void removeUpdateQueue(String tableName, List<Long> keyList) throws DCacheException {
		String[] memkeys = new String[keyList.size()];
		for(int i=0;i<keyList.size();i++) {
			memkeys[i] = genUpdateQueueKeyLevel2(tableName, keyList.get(i));
		}
		client.del(memkeys);//只移除 memkey2即可,memkey 会被线程pop出
	}
    
    /**
     * 
     * @param tableName
     * @param id
     * @throws DCacheException
     */
	protected void putDelQueue(String tableName, Long... id) throws DCacheException {
		removeUpdateQueue(tableName, Arrays.asList(id));//先从更新队列中移除
		String memKey = genDelQueueKey(tableName);//set形式
		String[] strids = new String[id.length]; 
		for(int i=0;i<strids.length;i++) {
			strids[i] = String.valueOf(id[i]);
		}
		client.sadd(memKey, strids);//再放入删除队列
	}
	
	protected void removeDelQueue(String tableName, Long... id) throws DCacheException {
		String memKey = genDelQueueKey(tableName);//set形式
		String[] strids = new String[id.length]; 
		for(int i=0;i<strids.length;i++) {
			strids[i] = String.valueOf(id[i]);
		}
		client.sremove(memKey, strids);
	}
	@Override
	public void delEntity(String tableName, long id, boolean flushdb)
			throws DCacheException {

		if (flushdb) {
			// 删除
			deleteDbValue(tableName, id);
		} else {
			putDelQueue(tableName, id);
		}

		client.del(genMemcKey(tableName, id));

		delTimes.getAndAdd(1);
		if (logger.isDebugEnabled()) {
			AtomicLong ato = new AtomicLong();
			AtomicLong old = del.putIfAbsent(tableName, ato);
			if (old != null) {
				ato = old;
			}
			ato.addAndGet(1);
		}
	}
	
	@Override
	public void delEntityList(String tableName, List<Long> keyList,
			boolean flushdb) throws DCacheException {
		try {

			String[] memKeys = new String[keyList.size()];
			for (int i = 0; i < keyList.size(); i++) {
				long id = keyList.get(i);
				memKeys[i] = genMemcKey(tableName, id);
			}
			if (flushdb) {
				deleteDbBatch(tableName, keyList);
			} else {
				// 放入队列
				putDelQueue(tableName, (Long[]) keyList.toArray());
			}
			client.del(memKeys);

			delListTimes.getAndAdd(1);
			if (logger.isDebugEnabled()) {
				AtomicLong ato = new AtomicLong();
				AtomicLong old = del.putIfAbsent(tableName, ato);
				if (old != null) {
					ato = old;
				}
				ato.addAndGet(1);
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e.getMessage(), e);
			throw new DCacheException(e);
		}
	}
	
	@Override
	public String getEntity(String tableName, long id) throws DCacheException {
		String result = null;
		try{
			String memKey = genMemcKey(tableName, id);
			result = client.get(memKey);
			if(result != null) {
				getFromMemTimes.getAndAdd(1);
				return result;
			}
			//从删除队列里找
			if(delQueueContainsKey(tableName, id)) {
				return result;
			}
			result = getDbValue(tableName, id);
			if(result != null) {
				client.set(genMemcKey(tableName, id), result,DEFAULT_EXPIRE);
			}
			
		}catch(Exception e) {
			e.printStackTrace();
			logger.error(e.getMessage(), e);
			throw new DCacheException(e);
		}
		return result;
	}
	
	/**
	 * 从db中获取数据 json串形式
	 * @param tableName
	 * @param id
	 * @return
	 * @throws DCacheException
	 */
	protected abstract String getDbValue(String tableName,long id) throws DCacheException;
	
	protected abstract void putDbValue(String tableName,LinkedHashMap<String,String> changed) throws DCacheException;
	
	protected abstract void deleteDbBatch(String tableName,List<Long> keyList) throws DCacheException;
	
	protected abstract void deleteDbValue(String tableName,long id) throws DCacheException;
	
	@Override
	public Map<Long, String> getEntityList(String tableName, List<Long> keyList) throws DCacheException {
		Map<Long,String> entityMap=new LinkedHashMap<Long,String>();
		try{
			String[] tmp = new String[keyList.size()];//key存放memkey value key
			
			for(int i=0;i<keyList.size();i++) {
				tmp[i] = genMemcKey(tableName, keyList.get(i));
			}
			List<String> ret = client.mgets(tmp);
			
			for(int i=0;i<ret.size();i++) {
				String v = ret.get(i);
				long id = keyList.get(i);
				if(v == null && !delQueueContainsKey(tableName, id)) {
					v = getDbValue(tableName, id);
					if(v != null) {
						client.set(genMemcKey(tableName, id), v,DEFAULT_EXPIRE);
					}
				}
				if(v != null) {
					entityMap.put(id, v);
				}
			}
		}catch(Exception e){
			e.printStackTrace();
			logger.error(e.getMessage(), e);
			throw new DCacheException(e);
		}
		return entityMap;
	}
	
	@Override
	public String getOnlyCache(String key) throws DCacheException {
		String result = null;
		try{
			result = client.get(genMemcKey(ONLY_CACHE_KEY, key));
			getFromMemTimes.getAndAdd(1);
		}catch(Exception e) {
			e.printStackTrace();
			logger.error(e.getMessage(), e);
			throw new DCacheException(e);
		}
		return result;
	}
	
	@Override
	public void putOnlyCache(String key, String value, int timeout)
			throws DCacheException {
		try {
			String memKey=this.genMemcKey(ONLY_CACHE_KEY, key);
			client.set(memKey, value,timeout);
			putTimes.getAndAdd(1);
		} catch (Exception e) {
			e.printStackTrace();
			throw new DCacheException(e);
		}
	}
	
	@Override
	public void putOnlyCacheIfAbsent(String key, String value)
			throws DCacheException {
		client.setIfAbsent(genMemcKey(ONLY_CACHE_KEY, key), value);
	}
	
	@Override
	public long increment(String key, int num, int expire)
			throws DCacheException {
		long ret = increment(key, num);
		client.setExpire(genMemcKey(ONLY_CACHE_KEY, key), expire);
		return ret;
	}

	@Override
	public long increment(String key) throws DCacheException {
		
		return client.increment(genMemcKey(ONLY_CACHE_KEY, key));
	}
	
	@Override
	public long increment(String key, int num) throws DCacheException {
		String memkey = genMemcKey(ONLY_CACHE_KEY, key);
		long ret = client.increment(memkey, num);
		return ret;
	}

	@Override
	public long decrement(String key, int num, int expire)
			throws DCacheException {
		long ret = decrement(key, num);
		client.setExpire(genMemcKey(ONLY_CACHE_KEY, key), expire);
		return ret;
	}

	@Override
	public long decrement(String key, int num) throws DCacheException {
		long ret = client.decrement(genMemcKey(ONLY_CACHE_KEY, key), num);
		return ret;
	}
	
	@Override
	public long decrement(String key) throws DCacheException {
		return client.decrement(genMemcKey(ONLY_CACHE_KEY, key));
	}

	@Override
	public long delOnlyCache(String key) throws DCacheException {
		return client.del(genMemcKey(ONLY_CACHE_KEY, key));
	}

	@Override
	public boolean lockRecord(String key) throws DCacheException {
		return client.setIfAbsent(genMemcKey(ONLY_CACHE_KEY, key), key, LOCK_TIMEOUT);
	}

	@Override
	public boolean releaseRecord(String key) throws DCacheException {
		return delOnlyCache(key) == 1l;
	}
	@Override
	public int getStoryType() {
		return storeType;
	}
}
